package httplibs

import (
	"embed"
	"gitee.com/captials-team/ubdframe/src/common"
	"gitee.com/captials-team/ubdframe/src/common/utils"
	v1log "gitee.com/captials-team/ubdframe/src/pkg/logs"
	"github.com/gin-contrib/multitemplate"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/render"
	"html/template"
	"net/http"
	"path/filepath"
	"strings"
)

// DefaultViewHandlerConfig 默认配置
var DefaultViewHandlerConfig = ViewHandlerConfig{
	ParamName: "name",
}

// ViewHandlerConfig viewHandler配置
type ViewHandlerConfig struct {
	ViewFs  embed.FS //模板渲染的viewFs
	ViewDir string   //模板渲染所在目录

	FuncMaps []template.FuncMap //模板文件迁入的函数map

	ParamName string                       //获取view渲染模板名称的参数名（如name）
	ParamFunc func(r *http.Request) string //定制化获取view渲染模板名称的方法(不存在则默认方式获取)

	OptionLayoutFlag string //布局文件前缀标志
}

// ViewHandler viewHandler
type ViewHandler struct {
	ViewHandlerConfig
	htmlRender render.HTMLRender
	v1log.InvokeLog
}

func NewViewHandler(cnf ViewHandlerConfig) *ViewHandler {
	h := &ViewHandler{
		ViewHandlerConfig: cnf,
	}

	return h
}

func (h *ViewHandler) init() {
	if h.ParamName == "" {
		h.ParamName = "name"
	}

	if h.ViewDir != "" {
		h.htmlRender = h.loadRenderFromDir(h.ViewDir, h.FuncMaps...)
	} else {
		h.htmlRender = h.loadRenderFromFs(h.ViewFs, h.FuncMaps...)
	}
}

// InitiateRender 初始化渲染器
func (h *ViewHandler) InitiateRender() {
	h.init()
}

func (h *ViewHandler) Render() render.HTMLRender {
	return h.htmlRender
}

func (h *ViewHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var name string
	if h.ParamFunc != nil {
		name = h.ParamFunc(r)
	} else {
		name = r.Form.Get(h.ParamName)
	}
	instance := h.htmlRender.Instance(h.templateName(name), map[string]interface{}{
		"Request": r,
	})
	if err := instance.Render(w); err != nil {
		panic(err)
	}
}

// GinRouter 推荐的ginRouter配置
func (h *ViewHandler) GinRouter(r *gin.Engine, prefix string) {
	prefix = utils.KeepHasPrefix(prefix, "/")

	r.GET(prefix, h.ViewForGin)
	r.GET(prefix+"/:name", h.ViewForGin)
}

func (h *ViewHandler) GinRouterJump(r *gin.Engine, prefix string) {
	h.GinRouter(r, prefix)
	r.NoRoute(func(ctx *gin.Context) {
		ctx.Redirect(http.StatusMovedPermanently, prefix)
	})
}

func (h *ViewHandler) ViewForGin(ctx *gin.Context) {
	var name string
	defer func() {
		if err := recover(); err != nil {
			h.Error("view err %s", err)
			if h.templateName(name) != "index.html" {
				h.renderForGin(ctx, "index")
				return
			}
			ctx.String(http.StatusNotFound, "")
			return
		}
	}()
	if h.ParamFunc != nil {
		name = h.ParamFunc(ctx.Request)
	} else {
		name = ctx.Params.ByName(h.ParamName)
	}
	//h.Info("ViewForGin %s", name)
	h.renderForGin(ctx, name)
}

func (h *ViewHandler) renderForGin(ctx *gin.Context, name string) {
	ctx.Render(http.StatusOK, h.htmlRender.Instance(h.templateName(name), gin.H{
		"Ctx":     ctx,
		"Request": ctx.Request,
	}))
}

func (h *ViewHandler) templateName(name string) string {
	if name == "" {
		name = "index.html"
	}
	if !strings.HasSuffix(name, ".html") {
		name = name + ".html"
	}
	return name
}

// loadRenderFromFs 使用FS做template
func (h *ViewHandler) loadRenderFromFs(embFs embed.FS, funcs ...template.FuncMap) multitemplate.Renderer {
	r := multitemplate.NewRenderer()
	templates := [][2]string{}
	layouts := []string{}
	layoutPrefix := h.LayoutFilePrefix()

	//扫描分为模板文件和布局文件（l_开头的为布局文件）
	err := utils.ScanEmbedFsDo(embFs, func(file string, path string) error {
		if strings.HasPrefix(file, layoutPrefix) {
			layouts = append(layouts, path)
			//logger.Info("content:%s", bt)
		} else {
			layouts = append(layouts, path)
			templates = append(templates, [2]string{file, path})
		}
		return nil
	})
	common.ErrPanic(err)
	if err != nil {
		h.Error("scan embed err: %s", err)
		return r
	}
	//h.Info("templates %+v", templates)

	for _, f := range templates {
		file := f[0]
		path := f[1]

		tpl := template.New(file)
		for _, v := range funcs {
			tpl = tpl.Funcs(v)
		}
		tmpl := template.Must(tpl.ParseFS(embFs, append(layouts, path)...))
		r.Add(file, tmpl)

		//debug
		//if file == "index.html" {
		//	tmpl.ExecuteTemplate(os.Stdout, file, map[string]string{
		//		"action": "xxx",
		//	})
		//}

		//h.Info("%s", path)
		//h.Info("templateAdd %s,%+v,%s", file, layouts, path)
	}

	return r
}

// loadRenderFromDir 使用目录做template
func (h *ViewHandler) loadRenderFromDir(dir string, funcs ...template.FuncMap) multitemplate.Renderer {
	r := multitemplate.NewRenderer()
	templates := [][2]string{}
	layouts := []string{}
	layoutPrefix := h.LayoutFilePrefix()

	//扫描分为模板文件和布局文件（l_开头的为布局文件）
	err := utils.ScanDirFileDo(dir, func(file string, path string) error {
		if strings.HasPrefix(file, layoutPrefix) {
			layouts = append(layouts, path)
			//logger.Info("content:%s", bt)
		} else {
			layouts = append(layouts, path)
			templates = append(templates, [2]string{file, path})
		}
		return nil
	})

	common.ErrPanic(err)
	if err != nil {
		h.Error("scan dir files err: %s", err)
		return r
	}
	//h.Info("templates %+v", templates)

	for _, f := range templates {
		file := f[0]
		path := f[1]

		tpl := template.New(file)
		for _, v := range funcs {
			tpl = tpl.Funcs(v)
		}
		tmpl := template.Must(tpl.ParseFiles(append(layouts, path)...))
		r.Add(file, tmpl)

		//debug
		//if file == "index.html" {
		//	tmpl.ExecuteTemplate(os.Stdout, file, map[string]string{
		//		"action": "xxx",
		//	})
		//}

		//h.Info("%s", path)
		//h.Info("templateAdd %s,%+v,%s", file, layouts, path)
	}

	return r
}

// LayoutFilePrefix 布局文件前缀
func (h *ViewHandler) LayoutFilePrefix() string {
	if len(h.OptionLayoutFlag) > 0 {
		return h.OptionLayoutFlag
	}
	return "l_"
}

// loadTemplates 直接使用目录
func (h *ViewHandler) loadTemplates(templatesDir string) multitemplate.Renderer {
	r := multitemplate.NewRenderer()

	layouts, err := filepath.Glob(templatesDir + "/layouts/*.html")
	if err != nil {
		panic(err.Error())
	}

	includes, err := filepath.Glob(templatesDir + "/includes/*.html")
	if err != nil {
		panic(err.Error())
	}

	// Generate our templates map from our layouts/ and includes/ directories
	for _, include := range includes {
		layoutCopy := make([]string, len(layouts))
		copy(layoutCopy, layouts)
		files := append(layoutCopy, include)
		r.AddFromFiles(filepath.Base(include), files...)
	}
	return r
}
